package org.mctourney.autoreferee.listeners;

import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerPickupItemEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.mctourney.autoreferee.AutoRefMatch;
import org.mctourney.autoreferee.AutoRefPlayer;
import org.mctourney.autoreferee.AutoRefTeam;
import org.mctourney.autoreferee.AutoReferee;
import org.mctourney.autoreferee.AutoRefMatch.RespawnMode;
import org.mctourney.autoreferee.goals.CoreGoal;
import org.mctourney.autoreferee.util.AchievementPoints;
import org.mctourney.autoreferee.util.BlockData;

public class ObjectiveTracker implements Listener
{
	AutoReferee plugin = null;

	public ObjectiveTracker(Plugin p)
	{
		plugin = (AutoReferee) p;
	}

	// Note: Wool on VM has been moved to ObjectiveTracer

	/* TRACKING OBJECTIVES/BED */

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void bedBreak(BlockBreakEvent event)
	{
		Player pl = event.getPlayer();
		Block block = event.getBlock();

		AutoRefMatch match = plugin.getMatch(block.getWorld());
		AutoRefPlayer apl = match == null ? null : match.getPlayer(pl);

		if (match != null && apl != null && match.getCurrentState().inProgress()
			&& match.getRespawnMode() == RespawnMode.BEDS_ONLY && block.getType() == Material.BED_BLOCK)
				match.new BedUpdateTask(apl).runTask(plugin);
	}

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void bedExplode(EntityExplodeEvent event)
	{
		Entity ent = event.getEntity();
		AutoRefMatch match = plugin.getMatch(ent.getWorld());
		boolean bedBroke = false;

		for (Block b : event.blockList())
			if (b.getType() == Material.BED_BLOCK) bedBroke = true;

		if (match != null && match.getCurrentState().inProgress()
			&& match.getRespawnMode() == RespawnMode.BEDS_ONLY && bedBroke)
				match.new BedUpdateTask(ent).runTask(plugin);
	}

	// ----------------- START WINCONDITION -----------------------

	private void _checkWinConditions(BlockEvent event)
	{
		AutoRefMatch match = plugin.getMatch(event.getBlock().getWorld());
		if (match != null) match.checkWinConditions();
	}

	private void _checkWinConditions(EntityEvent event)
	{
		AutoRefMatch match = plugin.getMatch(event.getEntity().getWorld());
		if (match != null) match.checkWinConditions();
	}

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(BlockBreakEvent event)
	{ _checkWinConditions(event); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(BlockBurnEvent event)
	{ _checkWinConditions(event); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(BlockFadeEvent event)
	{ _checkWinConditions(event); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(BlockFromToEvent event)
	{
		AutoRefMatch match = plugin.getMatch(event.getBlock().getWorld());
		if (match != null) for (AutoRefTeam team : match.getTeams())
			for (CoreGoal goal : team.getTeamGoals(CoreGoal.class))
				goal.checkSatisfied(event);

		// typical win condition check as well
		_checkWinConditions(event);
	}

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(BlockGrowEvent event)
	{ _checkWinConditions(event); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(BlockIgniteEvent event)
	{ _checkWinConditions(event); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(BlockPhysicsEvent event)
	{ _checkWinConditions(event); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(BlockPistonExtendEvent event)
	{ _checkWinConditions(event); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(BlockPistonRetractEvent event)
	{ _checkWinConditions(event); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(BlockRedstoneEvent event)
	{ _checkWinConditions(event); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(LeavesDecayEvent event)
	{ _checkWinConditions(event); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void checkWinConditions(PlayerDeathEvent event)
	{ _checkWinConditions(event); }

	// ------------------ END WINCONDITION ------------------------

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void blockBreak(BlockBreakEvent event)
	{
		AutoRefMatch match = plugin.getMatch(event.getBlock().getWorld());
		if (match != null) match.checkWinConditions();
	}

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void blockInteract(PlayerInteractEvent event)
	{
		if (event.hasBlock())
		{
			AutoRefMatch match = plugin.getMatch(event.getClickedBlock().getWorld());
			if (match != null) match.checkWinConditions();
		}
	}

	/* TRACKING WOOL CARRYING */

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void inventoryClick(InventoryClickEvent event)
	{ inventoryChange(event.getWhoClicked()); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void itemDrop(PlayerDropItemEvent event)
	{ inventoryChange(event.getPlayer()); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void itemPickup(PlayerPickupItemEvent event)
	{ inventoryChange(event.getPlayer()); }

	@EventHandler(priority=EventPriority.MONITOR)
	public void blockPlaceInventory(BlockPlaceEvent event)
	{ inventoryChange(event.getPlayer()); }

	class InventoryChangeTask implements Runnable
	{
		AutoRefPlayer apl = null;

		public InventoryChangeTask(AutoRefPlayer apl)
		{ this.apl = apl; }

		public void run()
		{ if (apl != null) apl.updateCarrying(); }
	}

	public void inventoryChange(HumanEntity entity)
	{
		AutoRefMatch match = plugin.getMatch(entity.getWorld());
		if (match == null) return;

		if (match.getCurrentState().inProgress() &&
			entity.getType() == EntityType.PLAYER)
		{
			AutoRefPlayer apl = match.getPlayer((Player) entity);
			if (apl != null) plugin.getServer().getScheduler()
				.runTask(plugin, new InventoryChangeTask(apl));
		}
	}

	/* TRACKING PLAYER HEALTH, HUNGER, AND ARMOR */

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void playerHealthDown(EntityDamageEvent event)
	{ healthArmorChange(event.getEntity()); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void playerHealthUp(EntityRegainHealthEvent event)
	{ healthArmorChange(event.getEntity()); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void playerFoodChange(FoodLevelChangeEvent event)
	{
		AutoRefMatch match = plugin.getMatch(event.getEntity().getWorld());
		if (match != null && event.getEntity().getType() == EntityType.PLAYER)
		{
			Player player = (Player) event.getEntity();
			match.messageReferees("player", player.getName(),
				"hunger", Integer.toString(event.getFoodLevel()));
		}
	}

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void playerArmorChange(InventoryClickEvent event)
	{ healthArmorChange(event.getWhoClicked()); }

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void playerArmorDrop(PlayerDropItemEvent event)
	{ healthArmorChange(event.getPlayer()); }

	class HealthArmorChangeTask implements Runnable
	{
		AutoRefPlayer apl = null;

		public HealthArmorChangeTask(AutoRefPlayer apl)
		{ this.apl = apl; }

		public void run()
		{ if (apl != null) apl.updateHealthArmor(); }
	}

	public void healthArmorChange(Entity entity)
	{
		AutoRefMatch match = plugin.getMatch(entity.getWorld());
		if (match != null && entity.getType() == EntityType.PLAYER)
		{
			AutoRefPlayer apl = match.getPlayer((Player) entity);
			if (apl != null) plugin.getServer().getScheduler()
				.runTask(plugin, new HealthArmorChangeTask(apl));
		}
	}

	/* MISC */

	@EventHandler(priority=EventPriority.MONITOR)
	public void playerRespawn(PlayerRespawnEvent event)
	{
		Player player = event.getPlayer();
		inventoryChange(player);
		healthArmorChange(player);

		AutoRefMatch match = plugin.getMatch(event.getRespawnLocation().getWorld());
		if (match != null && match.getRespawnMode() == RespawnMode.BEDS_ONLY)
			if (player.getBedSpawnLocation() == null)
			{
				match.eliminatePlayer(player);
				event.setRespawnLocation(match.getSpectatorSpawn());
			}
	}

	private class CraftCountTask extends BukkitRunnable
	{
		private HumanEntity player;
		private int count;
		private Material type;
		private MaterialData data;
		private int size;

		public CraftCountTask(ItemStack item, HumanEntity p)
		{
			this.player = p;
			this.type = item.getType();
			this.data = item.getData();
			this.count = countItemInInventory();
			this.size = item.getAmount();
		}

		private int countItemInInventory()
		{
			int c = 0;
			if (player.getItemOnCursor() != null)
			{
				ItemStack item = player.getItemOnCursor();
				if (item.getType().equals(this.type) && item.getData().equals(this.data))
					c += item.getAmount();
			}
			for (ItemStack item : player.getInventory().getContents())
				if (item != null && item.getType().equals(this.type) && item.getData().equals(this.data))
					c += item.getAmount();
			return c;
		}

		@Override
		public void run()
		{
			int crafted = countItemInInventory() - this.count;
			AutoRefMatch match = plugin.getMatch(player.getWorld());
			if (match == null || !match.getCurrentState().inProgress()) return;

			AutoRefPlayer apl = match.getPlayer((Player) player);
			AchievementPoints ach = AchievementPoints.getEquipmentCraft(type);
			if (apl != null) apl.addPoints(ach, crafted / this.size);
		}
	}

	@EventHandler(priority=EventPriority.HIGHEST, ignoreCancelled=true)
	public void itemCraft(CraftItemEvent event)
	{
		AutoRefMatch match = plugin.getMatch(event.getWhoClicked().getWorld());
		if (match == null) return;

		if (!(event.getWhoClicked() instanceof Player)) return;
		AutoRefTeam team = plugin.getTeam((Player) event.getWhoClicked());

		BlockData recipeTarget = BlockData.fromItemStack(event.getRecipe().getResult());
		if (team != null && !team.canCraft(recipeTarget)) event.setCancelled(true);

		// if this is on the blacklist, cancel
		if (!match.canCraft(recipeTarget)) event.setCancelled(true);
	}

	@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
	public void itemCraftMonitor(CraftItemEvent event)
	{
		new CraftCountTask(event.getRecipe().getResult(), event.getWhoClicked()).runTask(plugin);
	}
}
